﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a forum member.
    /// </summary>
    public class CreateForumMember : CreateAccountProfile<ForumMemberEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FORUM");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .Rule((ctx, uow) => ctx.Account(uow).EntityID == AccountID);
            }
        }

        /// <summary>
        /// Gets or sets the avatar file identifier.
        /// </summary>
        public int AvatarFileID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the signature.
        /// </summary>
        public string Signature
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public ForumMemberFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Construct(IUnitOfWork uow, ForumMemberEntity entity)
        {
            base.Construct(uow, entity);

            entity.AvatarFile = AvatarFileID != 0 
                ? uow.Execute(new GetFile
                {
                    ThrowsException = true,
                    EntityID = AvatarFileID
                }).Result 
                : null;
            entity.Signature = StringHelper.EnsureNotNull(Signature).TrimToLen(1000);
            entity.Flags = Flags;
        }

        protected override void Work(IUnitOfWork uow, out ForumMemberEntity result)
        {
            base.Work(uow, out result);
            try
            {
                uow.Execute(new EnqueueForumMemberNotification
                {
                    MemberID = result.EntityID,
                    RecipientID = result.EntityID
                });
            }
            catch(Exception ex)
            {
                uow.Execute(new CreateLogRecordFromException
                {
                    Ex = ex
                });
            }
        }

        #endregion
    }

    /// <summary>
    /// Updates the forum member.
    /// </summary>
    public class UpdateForumMember : UpdateAccountProfile<ForumMemberEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FORUM");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .Rule((ctx, uow) =>
                    {
                        var member = ctx.ForumMember(uow);
                        return member != null && member.EntityID == EntityID;
                    });
            }
        }

        /// <summary>
        /// Gets or sets the avatar file identifier.
        /// </summary>
        public int AvatarFileID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the signature.
        /// </summary>
        public string Signature
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public ForumMemberFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Revise(IUnitOfWork uow, ForumMemberEntity entity)
        {
            base.Revise(uow, entity);

            entity.AvatarFile = AvatarFileID != 0 
                ? uow.Execute(new GetFile
                {
                    ThrowsException = true,
                    EntityID = AvatarFileID
                }).Result 
                : null;
            entity.Signature = StringHelper.EnsureNotNull(Signature).TrimToLen(1000);
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the forum member statistics.
    /// </summary>
    public class UpdateForumMemberStats : OperateEntityByID<ForumMemberEntity>
    {
        #region Utilities

        protected override void Work(IUnitOfWork uow, out ForumMemberEntity result)
        {
            var member = uow.Execute(new GetForumMember
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;

            uow.Flush();
            uow.SqlQuery("exec fwk_ForumMembers_UpdateStats @MemberID=:MemberID, @UpdatedOn=:UpdatedOn")
                .SetInt32("MemberID", member.EntityID)
                .SetDateTime("UpdatedOn", DateTime.UtcNow)
                .ExecuteUpdate();
            uow.Flush();
            
            result = uow.Execute(new RefreshEntity<ForumMemberEntity>
            {
                Entity = member
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the forum member.
    /// </summary>
    public class DeleteForumMember : DeleteAccountProfile<ForumMemberEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FORUM");
            }
        }

        #endregion
    }

    /// <summary>
    /// Gets the forum member by identifier.
    /// </summary>
    public class GetForumMember : GetAccountProfile<ForumMemberEntity>,
        IApiOperation
    {
    }

    /// <summary>
    /// Gets the forum member by account identifier.
    /// </summary>
    public class GetForumMemberByAccount : GetAccountProfileByAccount<ForumMemberEntity>,
        IApiOperation
    {
        #region Options

        protected override bool Cacheable
        {
            get
            {
                return true;
            }
        }

        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over forum member collection.
    /// </summary>
    public class SearchForumMembers : SearchOperation<ForumMemberEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchForumMembers()
        {
            Term = SearchTerm.Empty;
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                ForumMemberSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the rank identifier.
        /// </summary>
        public int? RankID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the topic count "from".
        /// </summary>
        public int? TopicCountFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the topic count "to".
        /// </summary>
        public int? TopicCountTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the post count "from".
        /// </summary>
        public int? PostCountFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the post count "to".
        /// </summary>
        public int? PostCountTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public ForumMemberFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public ForumMemberSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<ForumMemberEntity> Build(IUnitOfWork uow, IQueryable<ForumMemberEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Account);
            query = query.Fetch(x => x.Rank);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByNumRange(x => x.TopicCount, TopicCountFrom, TopicCountTo);
            query = query.FilterByNumRange(x => x.PostCount, PostCountFrom, PostCountTo);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.Account.Email.StartsWith(Term.Keyword) ||
                        x.Account.Username.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.Account.Email.EndsWith(Term.Keyword) ||
                        x.Account.Username.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.Account.Email.Contains(Term.Keyword) ||
                        x.Account.Username.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.Account.Email.Equals(Term.Keyword) ||
                        x.Account.Username.Equals(Term.Keyword));
                    break;
            }
            if(RankID.HasValue)
                query = query.Where(x => x.RankID == RankID.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case ForumMemberSortBy.AccountEmail:
                            query = query.OrderBy(x => x.Account.Email, SortDir, i != 0);
                            break;
                        case ForumMemberSortBy.AccountUsername:
                            query = query.OrderBy(x => x.Account.Username, SortDir, i != 0);
                            break;
                        case ForumMemberSortBy.TopicCount:
                            query = query.OrderBy(x => x.TopicCount, SortDir, i != 0);
                            break;
                        case ForumMemberSortBy.PostCount:
                            query = query.OrderBy(x => x.PostCount, SortDir, i != 0);
                            break;
                        case ForumMemberSortBy.KarmaPoints:
                            query = query.OrderBy(x => x.KarmaPoints, SortDir, i != 0);
                            break;
                        case ForumMemberSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    /// <summary>
    /// Gets the collection of forum members as a dictionary, where 
    /// key - forum member identifier and value - account email.
    /// </summary>
    public class GetForumMemberDictionary : SearchOperation<ForumMemberEntity, Int32, String>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public GetForumMemberDictionary()
        {
            Term = SearchTerm.Empty;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public ForumMemberFlags FlagMask
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<ForumMemberEntity> Build(IUnitOfWork uow, IQueryable<ForumMemberEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x =>
                        x.Account.Email.StartsWith(Term.Keyword) ||
                        x.Account.Username.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x =>
                        x.Account.Email.EndsWith(Term.Keyword) ||
                        x.Account.Username.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x =>
                        x.Account.Email.Contains(Term.Keyword) ||
                        x.Account.Username.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x =>
                        x.Account.Email.Equals(Term.Keyword) ||
                        x.Account.Username.Equals(Term.Keyword));
                    break;
            }
            query = query.OrderBy(x => x.Account.Email);
            return query;
        }

        protected override IQueryable<KeyValuePair<Int32, String>> Map(IQueryable<ForumMemberEntity> query)
        {
            return query.Select(x => new KeyValuePair<Int32, String>(x.EntityID, x.Account.Email));
        }

        #endregion
    }

    #endregion

    #region __Notification__

    /// <summary>
    /// Enqueues a forum member notification message.
    /// </summary>
    /// <remarks>
    /// Message template code: IFRMMMBR
    /// Allowed tokens: [T:Recipient.EntityID], [T:Recipient.Guid], [T:Recipient.Email], [T:Recipient.Username],
    /// [T:ForumMember.Account.EntityID], [T:ForumMember.Account.Guid], [T:ForumMember.Account.Email],
    /// [T:ForumMember.Account.Username], [T:ForumMember.Rank.EntityID], [T:ForumMember.Rank.Name],
    /// [T:ForumMember.EntityID], [T:ForumMember.PostCount], [T:ForumMember.TopicCount], 
    /// [T:ForumMember.KarmaPoints]
    /// </remarks>
    public class EnqueueForumMemberNotification : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the forum member identifier.
        /// </summary>
        public int MemberID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient account identifier.
        /// </summary>
        public int RecipientID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var member = uow.Execute(new GetForumMember
            {
                ThrowsException = true,
                EntityID = MemberID
            }).Result;
            var recipient = member.Account.EntityID == RecipientID ? member.Account : uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = RecipientID
            }).Result;
            var usrLocale = recipient.LocalizationProfile;
            var rank = member.Rank;
            var t_rank = rank != null ? rank.Translation(usrLocale.Language.EntityID) : null;

            result = uow.Execute(new CreateTemplatedMessageByCode
            {
                TemplateCode = "IFRMMMBR",
                LanguageID = usrLocale.Language.EntityID,
                Replacements = new Dictionary<String, String>
                {
                    {"Recipient.EntityID", recipient.EntityID.ToString()},
                    {"Recipient.Email", recipient.Email},
                    {"Recipient.Username", recipient.Username},
                    {"Recipient.Guid", recipient.Guid.ToString()},
                    {"ForumMember.Account.EntityID", member.Account.EntityID.ToString()},
                    {"ForumMember.Account.Email", member.Account.Email},
                    {"ForumMember.Account.Username", member.Account.Username},
                    {"ForumMember.Account.Guid", member.Account.Guid.ToString()},
                    {"ForumMember.Rank.EntityID", rank != null 
                        ? rank.EntityID.ToString() 
                        : String.Empty},
                    {"ForumMember.Rank.Name", t_rank != null 
                        ? t_rank.Name 
                        : String.Empty},
                    {"ForumMember.EntityID", member.EntityID.ToString()},
                    {"ForumMember.PostCount", member.PostCount.ToString()},
                    {"ForumMember.KarmaPoints", member.KarmaPoints.ToString()},
                    {"ForumMember.TopicCount", member.TopicCount.ToString()}
                },
                To = recipient.Email,
                Flags = MessageFlags.Active
            }).Result;
        }

        #endregion
    }

    #endregion
}
